package com.sf.protal.common.log;

import com.sf.protal.admin.pojo.UmsAdmin;
import com.sf.protal.admin.service.UmsUserService;
import com.sf.protal.common.json.JsonUtils;
import com.sf.protal.common.log.annotation.SystemControllerLog;
import com.sf.protal.common.log.annotation.SystemServiceLog;
import com.sf.protal.common.log.service.SysLogService;
import com.sf.protal.common.utils.DateUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/**
 * 系统日志aop
 */
@Aspect
@Component
public class SystemLogAspect {

	private static Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);
	private static final ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<Date>("ThreadLocal beginTime");
	private static final ThreadLocal<SysLog> logThreadLocal = new NamedThreadLocal<SysLog>("ThreadLocal log");
	private static final ThreadLocal<UmsAdmin> currentUser = new NamedThreadLocal<>("ThreadLocal user");

	@Autowired(required = false)
	HttpServletRequest request;

	@Autowired
	ThreadPoolTaskExecutor threadPoolTaskExecutor;

	@Autowired
	SysLogService logService;

	@Autowired
	UmsUserService userService;

//	@Autowired
//	RedisUtil redisUtil;

	/**
	 * Service层切点
	 */
	@Pointcut(value = "@annotation(com.sf.protal.common.log.annotation.SystemServiceLog)")
	public void serviceAspect() {

	}

	/**
	 *      * Controller层切点 注解拦截
	 *     
	 */
	@Pointcut("@annotation(com.sf.protal.common.log.annotation.SystemControllerLog)")
	public void controllerAspect() {

	}

	@Pointcut("execution(* com.sf.protal.*.controller.*.*(..))")
	public void controllerPointerCut() {

	}

	/**
	 *      * 前置通知 用于拦截Controller层记录用户的操作的开始时间
	 *      * @param joinPoint 切点
	 *      * @throws InterruptedException
	 *     
	 */
	@Before("controllerAspect()")
	public void doBefore(JoinPoint joinPoint) throws InterruptedException {
		logger.info("进入日志切面前置通知!!");
		Date beginTime = new Date();
		beginTimeThreadLocal.set(beginTime);//线程绑定变量（该数据只有当前请求的线程可见）  
		if (logger.isDebugEnabled()) {//这里日志级别为debug
			logger.debug("开始计时: {}  URI: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
					.format(beginTime), request.getRequestURI());
		}
		//读取session中的用户
		HttpSession session = request.getSession();
		UmsAdmin user = (UmsAdmin) session.getAttribute("ums_admin");
		UmsAdmin admin = userService.getUser(user);
		currentUser.set(user);
	}

	/**
	 * 后置通知 用于拦截Controller层记录用户的操作
	 *  @param joinPoint 切点
	 *     
	 */
	@After("controllerAspect()")
	public void doAfter(JoinPoint joinPoint) {
		logger.info("进入日志切面后置通知!!");
		UmsAdmin user = currentUser.get();
		if (user != null) {
			String title = "";
			String type = "info"; //日志类型(info:入库,error:错误)
			String remoteAddr = request.getRemoteAddr();//请求的IP
			String requestUri = request.getRequestURI();//请求的Uri
			String method = request.getMethod();//请求的方法类型(post/get)
			Map<String, String[]> params = request.getParameterMap(); //请求提交的参数

			try {
				title = getControllerMethodDescription2(joinPoint);
			} catch (Exception e) {
				e.printStackTrace();
			}
			// 打印JVM信息。
			long beginTime = beginTimeThreadLocal.get().getTime();//得到线程绑定的局部变量（开始时间）
			Date beginDate = new Date(beginTime);
			long endTime = System.currentTimeMillis(); //2、结束时间
			Date endDate = new Date(endTime);
			if (logger.isDebugEnabled()) {
				logger.debug("计时结束：{}  URI: {}  耗时： {}   最大内存: {}m  已分配内存: {}m  已分配内存中的剩余空间: {}m  最大可用内存: {}m",
						new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(endTime),
						request.getRequestURI(),
						DateUtils.getDatePoor(endDate, beginDate),
						Runtime.getRuntime().maxMemory() / 1024 / 1024,
						Runtime.getRuntime().totalMemory() / 1024 / 1024,
						Runtime.getRuntime().freeMemory() / 1024 / 1024,

						(Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory() + Runtime.getRuntime().freeMemory()) / 1024 / 1024)
				;
			}
			logger.info("设置日志信息存储到表中!!");
			SysLog log = new SysLog();
			log.setTitle(title);
			log.setType(type);
			log.setRemoteAddr(remoteAddr);
			log.setRequestUri(requestUri);
			log.setMethod(method);
			log.setMapToParams(params);
			log.setException("无异常!");
			log.setUserId(user.getUsername());
			Date operateDate = beginTimeThreadLocal.get();
			log.setOperateDate(operateDate);
			log.setTimeout(DateUtils.getDatePoor(endDate, beginDate));
			//1.直接执行保存操作
			this.logService.addLog(log);
			//2.优化:异步保存日志
			Thread thread = new Thread(new SaveLogThread(log,logService));
			thread.start();
			//3.再优化:通过线程池来执行日志保存
			threadPoolTaskExecutor.execute(new SaveLogThread(log, logService));
			logThreadLocal.set(log);
		}

	}

	@AfterReturning(returning = "res", pointcut = "controllerAspect()")
	public void doAfterReturning(Object res) throws Throwable {
		// 处理完请求，返回内容
		logger.info("==========返回参数日志=========");
		logger.info("返回接口响应参数:" + JsonUtils.objectToJson(res));
		SysLog log = logThreadLocal.get();
		if (log != null) {
			log.setResultParams(JsonUtils.objectToJson(res));
			logger.info("==========更新日志参数=========");
			new UpdateLogThread(log, logService).start();
		}
	}

	/**
	 *      *  异常通知 记录操作报错日志
	 *      * @param joinPoint
	 *      * @param e
	 *     
	 */

	@AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
	public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
		logger.info("进入日志切面异常通知!!");
		logger.info("异常信息:" + e.getMessage());
		SysLog log = logThreadLocal.get();
		if (log != null) {
			log.setType("error");
			log.setException(e.toString());
			new UpdateLogThread(log, logService).start();
		}
	}

	/**
	 *      * 获取注解中对方法的描述信息 用于service层注解
	 *      * @param joinPoint切点
	 *      * @return discription
	 *     
	 */
	public static String getServiceMthodDescription2(JoinPoint joinPoint) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		Method method = signature.getMethod();
		SystemServiceLog systemServiceLog = method.getAnnotation(SystemServiceLog.class);
		String desc = systemServiceLog.desc();
		return desc;
	}

	/**
	 *      * 获取注解中对方法的描述信息 用于Controller层注解
	 *      *
	 *      * @param joinPoint 切点
	 *      * @return discription
	 *     
	 */
	public static String getControllerMethodDescription2(JoinPoint joinPoint) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		Method method = signature.getMethod();
		SystemControllerLog systemControllerLog = method.getAnnotation(SystemControllerLog.class);
		String desc = systemControllerLog.desc();
		return desc;
	}

	/**
	 *      * 保存日志线程
	 *     
	 */
	private static class SaveLogThread implements Runnable {

		private SysLog log;

		private SysLogService logService;

		public SaveLogThread(SysLog log, SysLogService logService) {
			this.log = log;
			this.logService = logService;
		}

		@Override
		public void run() {
			logService.addLog(log);
		}

	}

	/**
	 * 日志更新线程
	 */
	private static class UpdateLogThread extends Thread {

		private SysLog log;
		private SysLogService logService;

		public UpdateLogThread(SysLog log, SysLogService logService) {
			super(UpdateLogThread.class.getSimpleName());
			this.log = log;
			this.logService = logService;
		}

		@Override
		public void run() {
			this.logService.putLog(log);
		}
	}
}
